libraries linuxlibraries linux 
 

By zer0-absolu 17 novembre 2020 hacking 2 Comments

Linux: Les librairies

Bonjour à tous, j’aimerais en ce jour commencer le tuto par une citation, comme le disait très justement le célèbre philosophe Kierkegaard :  » La vie n’est pas un problème à résoudre mais une réalité qui doit être vécue ».

« wow c’est beau… quel est le rapport avec le tuto d’aujourd’hui ? »

Absolument aucun !

« …. »

Mouhahaha, bienvenue dans ce second tuto consacré à Linux, nous allons aborder aujourd’hui le fonctionnement des librairies ! Comme pour le précédent tuto je vais essayer de vous faire découvrir des aspects de Linux que vous ne connaissez peut être pas ou seulement de loin, en y intégrant un peu de notions de sécurité une fois les bases posées !

« Allez roule ma poule ! »

Pour suivre ce tuto il vous est conseillé d’avoir :

C’est parti !

I) Alexandrie… Alexandra ?

Il est bien beau de parler de bibliothèques, mais savez-vous ce qu’est une bibliothèque ?

« Bah c’est la ou on range des livres non ? »

Hum… regardons plutôt ce qu’en dit Wikipédia :

En informatique, une bibliothèque logicielle est une collection de routines, qui peuvent être déjà compilées et prêtes à être utilisées par des programmes. Les bibliothèques sont enregistrées dans des fichiers semblables, voire identiques aux fichiers de programmes, sous la forme d’une collection de fichiers de code objet rassemblés accompagnée d’un index permettant de retrouver facilement chaque routine. Le mot « librairie » est souvent utilisé à tort pour désigner une bibliothèque logicielle. Il s’agit d’un anglicisme fautif dû à un faux-ami (library).

https://fr.wikipedia.org/wiki/Biblioth%C3%A8que_logicielle

Avant que vous me fassiez remarquer que le tuto porte le nom ‘Librairie’ alors que pourtant Wikipédia dit que c’est un anglicisme, je tiens à vous dire que vous avez par définition, absolument raison. Néanmoins du au fait que nous utilisons souvent des anglicismes en informatique de manière générale, et que cela ne me dérange pas tant que la création de termes n’ajoute pas des erreurs de sémantiques, j’utiliserai donc le terme ‘librairie’ dans ce tuto.

Vous l’aurez donc compris les librairie sont des collections de fonctions que l’on peut utiliser pour des buts précis, un calcul spécifique , créer une nouvelle librairie à partir d’une première…

Le premier intérêt de cette pratique vous l’aurez compris est de pouvoir utiliser des fonctions déjà codées pour éviter par exemple de devoir recoder la fonction printf() à chaque fois que vous voulez afficher un ‘Hello World’ dans vos codes C favoris. La principe de librairie se retrouve pour quasiment tout les langages et tout les systèmes d’exploitations, et existent sous deux formes :

Les librairies statiques sont des librairies dont le contenu est directement intégré dans un programme lors de la compilation de celui-ci (lors de l’édition de liens), l’avantage est que le programme en question a à disposition tout le code nécessaire pour fonctionner que donc il n’y a pas besoin d’installer quoi que ce soit de plus pour qu’il fonctionne, mais en contrepartie le programme est plus lourd puisque en plus du code de votre programme il y aura le code des librairies intégré à celui-ci.

Les librairies dynamiques quant à elle sont des librairies chargées dynamiquement à l’exécution du programme par le système d’exploitation, on retrouvera alors dans le code de notre programme des références vers des fonctions, que notre système d’exploitation traitera pour nous donner accès au code demandé dans la librairie. L’avantage est que du coup le programme ne contenant pas le code des librairies, il s’en trouve plus léger, mais en contrepartie si nous ne disposez pas de la/des bonne(s) librairie(s) sur vôtre système d’exploitation, il faudra les installer pour faire fonctionner votre programme.

Il est à noter que le choix du type de librairie à utiliser incombe au programmeur, en effet c’est le programmeur qui va décider si sa librairie sera chargée statiquement ou dynamiquement dans son programme lors de la compilation, ainsi soit le contenu intégral des libraires sera chargé soit seulement les références des fonctions de celle-ci. Le contenu de la librairie restera le même au final c’est seulement la façon de l’intégrer à nos programmes qui change.

En résumé :

 

Statique

Dynamique

Avantage

« clés en main »

Plus léger

Inconvénient

Plus lourd

Besoin d’avoir les dépendances installées

II) Linux et les librairies

Comme nous l’avons vu précédemment, il existe deux types de librairies, bien que le contenu en lui même du fichier ne change pas, Linux associe deux extensions de fichiers différentes pour les libraires statiques et dynamiques.

Pour les libraires dynamique il existe un fichier de configuration sous linux qui regroupe les différents chemins pour accéder à celles-ci, ce fichier est ‘/etc/ld.so.conf‘ dont voici le contenu chez moi :

 

Et voici également le contenu de mon dossier ‘/etc/ld.so.conf.d/‘ qui est référencé :

 

Si on prend un des fichiers de configuration on pourra y retrouver les chemins d’accès aux librairies comme par exemple ici avec le fichier ‘/etc/ld.so.conf.d/x86_64-linux-gnu.conf‘ :

 

Petite remarque : les librairies dynamiques sont aussi appelées librairies partagées (« shared’ en anglais), il se peut que vous retrouviez ce terme sur le net n’en soyez pas étonné, d’ailleurs l’extension ‘.so‘ veut dire « shared object » (objet partagé).

III) Le livre te montrera la voie… de la librairie

Bon bon bon je sens que vous frémissez la et que vous avez envie de mettre les doigts dans le cambouis ! Rassurez-vous on va commencer de suite, mais tout de même en douceur.

Pour commencer nous allons voir comment on peut suivre les appels aux librairies de nos programmes préférés, pour faire simple nous allons utiliser un petit programme en C que vous connaissez bien :

#include <stdio.h>

int main() {

 

   printf("Hello, World!");

   return 0;

}

Compilons notre programme en utilisant la commande suivante, comme vous l’auriez certainement naturellement fait chez vous :

gcc hello.c -o hello

On obtient ainsi notre programme compilé. Et si nous utilisions la commande file dessus ? :

file hello

 

Ah ! Regardez , une information intéressante, si l’on demande à notre compilateur favoris gcc de compiler notre programmer sans lui donner d’arguments particuliers pour la compilation, il prend la décision de linker dynamiquement les librairies dont nôtre programme à besoin.

Il est même possible de voir les libraires utilisées par notre programme, pour ce faire utilisons la commande suivante :

ldd ./hello

 

On retrouve bien nos librairies dynamiques , vous vous souvenez l’extension est en « .so » !

« Oui on se souvient on est pas bêtes ça va… mais comment on fait si on veut que le compilateur link statiquement les librairies au fait ? »

Très bonne question, c’est très simple il suffit d’ajouter un argument à la commande de compilation :

gcc hello.c --static -o hello

Utilisons une nouvelle fois la commande file dessus :

 

Cette fois on voit clairement que le programme a été linké statiquement.

Maintenant que nous avons vu les bases, passons à l’étape suivante !

IV)  » .so , You wanna be a writer ? »

Comme le titre de cette étape a pu vous le faire deviner, nous allons maintenant écrire nous même une petite librairie pour pouvoir jouer avec plus facilement qu’avec les grosses librairies de Linux.

« Ah mais on peut créer nous même une librairie ? Mais elle sera statique ou dynamique ? »

Question légitime, et bien la réponse va vous plaire je pense, puisqu’il est non seulement possible de créer une librairie nous même comme dit précédemment, mais cela sera également à nous de choisir entre librairie statique ou librairie dynamique ! Nous pouvons faire ce que l’on veut ! Il suffit juste de le faire proprement…. plus ou moins…

« Donc comment on s’y prend du coup..? »

Et bien c’est très simple, commençons par écrire une fonction en C :

//kali_lib.c

#include <stdio.h>

 

void truc()

{

        puts("Snk was a lie");

}

//kali_lib.h

#ifndef kali_lib_h_

#define kali_lib_h_

 

extern void truc();

 

#endif

Nous avons donc ici une fonction qui s’appelle ‘truc’ et nous avons prit soin d’ajouter un fichier header pour que cela soit propre.

Maintenant utilisons notre fonction dans un programme :

//main.c

#include <stdio.h>

#include "kali_lib.h"

 

int main()

{

        truc();

        return 0;

}

Maintenant que nous avons tout à disposition pour continuer… continuons !

« … ça va finir par se voir que tu manques d’originalité »

J’ai oublié de vous dire mais , gardez ces 3 fichiers sous la main car ils vont nous servir pour la suite du tuto !

A) le cas de la librairie statique

Commençons par le cas le plus simple, le cas de la librairie statique.

Premièrement il nous faut créer le fichier objet dont ont se servira pour créer la librairie, en reprenant les fichiers précédemment fournit cela nous donne :

gcc -c kali_lib.c -o kali_lib.o

Maintenant nous allons créer la librairie, en fait une libraire statique n’est rien d’autre qu’une collection d’objets (donc des ‘.o‘ dans nôtre cas). Pour en créer une facilement procédons comme ceci :

ar rcs kali_lib.a kali_lib.o

Cette commande va permettre donc de créer la libraire statique ‘kali_lib.a’ et en même temps au passage de générer un index de contenu d’archive (grâce à l’option ‘s’). Pour ceux qui débutent je vous conseille de pas trop vous attarder sur le sujet, pour ceux qui ont déjà quelques connaissances dans le domaine, l’option ‘s’ fait exactement pareil que l’outil ranlib (dans les faits il est directement intégré a l’outil ar). Pour tout le monde, l’intérêt d’ajouter cette option est, entre autre, d’accélérer l’édition de lien.

Les options ‘r’ et ‘c’ quant à elles , servent respectivement à remplacer un fichier déjà existant dans l’archive par sa version plus récente ou bien de l’ajouter s’il le fichier n’existe pas, et de supprimer les messages d’erreurs si l’archive doit être créée.

Vérifions que l’archive soit bien créée en utilisant la commande file :

 

Tout semble en ordre ! Mais vérifions quand même un peu plus en détails, vous vous souvenez il y a quelques instants je vous parlais de l’option ‘s’ lors de la création de notre archive, et bien grâce à elle nous allons pouvoir lire directement l’index qu’elle créer. Pour se faire :

nm kali_lib.a

 

Et la nous pouvons vérifier que l’archive contient bien notre ‘.o‘ et nous avons même en prime la liste des fonctions de nôtre librairie, on peut voir d’ailleurs qu’il y a un appel vers une fonction externe à nôtre propre librairie , je parle bien entendu de la fonction puts.

Il ne reste plus qu’à compiler notre programme qui appelle la fonction présente dans nôtre librairie ! :

gcc main.c -L. -l:kali_lib.a -o program

Ici nous demandons à gcc de compiler nôtre programme en lui spécifiant au travers de l’option ‘-L’ le dossier dans lequel se situe nôtre librairie (ici nous avons mis un point pour spécifier le répertoire courant) et aussi au travers de l’option ‘-l’ le nom de la librairie à utiliser , une petite remarque ici d’ailleurs, le point après la première option est directement collé à l’option elle même, et il en est de même pour les ‘:’ après la seconde option. Une autre remarque à faire est qu’il est possible de se passer des ‘:’ sur la seconde option et de donner simplement le nom de notre librairie sans extension mais pour cela notre librairie doit respecter une convention de nommage :

Pour nous simplifier la tâche nous pouvons ainsi utiliser les ‘:’ et donner le nom complet de notre librairie avec son extension.

Désolé pour cette petite aparté mais je pense qu’il était intéressant de vous expliquer ces quelques conventions du compilateur gcc.

Pour finir nous avons plus qu’a vérifier si nôtre programme fonctionne bien :

 

Il semblerait que ce soit bon !

« Mais dis donc c’est bien beau le programme marche mais, j’ai vu une erreur! »

Ah et laquelle ?

« Et bien comme tu nous encourage souvent à utiliser l’outil ‘file’ j’ai utilisé ce même outil sur nôtre programme et j’ai vu que le programme est compilé DYNAMIQUEMENT ! »

Mmmmmmm regardons ensemble :

 
 

Effectivement , je vais devoir vous expliquer ce résultat, je ne vais pas m’étaler longuement sur le sujet car ce n’est pas vraiment le sujet de ce tuto mais simplement sachez que cela est du à la présence de l’include de ‘stdio.h’ dans notre programme, qui lui est une librairie dynamique. Mais je peux vous assurer que votre libraire est bien dans le programme de manière statique. Ce qui veux dire que vous pourriez transférer ce programme sur un autre Linux et qu’il se lancerait !

B) Le cas de la librairie dynamique

Maintenant que le cas facile est traité , passons au cas un peu plus complexe.

Nous allons utiliser les mêmes fichiers que fournit précédemment bien entendu mais nous allons changer les options de compilations pour créer une librairie dynamique.

Pour commencer créons le fichier objet un peu comme dans la partie A, sauf que cette fois ci nous allons ajouter une option à la compilation :

gcc -c kali_lib.c -fPIC -o kali_lib.o

l’option ‘-fPIC’ est obligatoire pour par la suite créer une librairie dynamique (partagée). PIC signifie « Position Independent Code » cela permet entre autre de générer un code assembleur qui n’a pas besoin d’avoir un emplacement spécifique en mémoire (notion d’adressage mémoire) pour fonctionner.

Ceci étant fait créons maintenant notre librairie partagée (j’utilise volontairement le terme partagée ici pour vous habituer à lire les deux termes, mais également car dans le cadre de l’utilisation de gcc c’est plus logique, vous allez voir de suite pourquoi) , pour se faire procédons ainsi :

gcc -shared kali_lib.o -o kali_lib.so

Une petite remarque s’impose ici, concernant les SONAMEs : Les SONAME sont simplement des noms sous forme de string qui permettent simplement de donner des noms logiques à des librairie, en pratique si une librairie se nomme ‘lib.so.2.5.8‘ , son SONAME sera ‘lib.so.2‘, je ne m’attarderai pas plus sur le sujet mais sachez simplement que cela sert à assurer une compatibilité des anciennes versions des librairies et que les librairies que nous allons créer n’auront pas de SONAME car nous n’en auront pas l’utilité ici.

Maintenant linkons nôtre programme avec nôtre librairie partagée :

gcc main.c -L. -l:kali_lib.so -o program2

Il ne reste plus qu’à lancer notre programme :

 
 

Ne vous inquiétez pas, car c’est en fait tout à fait normal.

Le système ne sait tout simplement pas ou se trouve la librairie en question. Malgré le fait que nous avons compiler notre programme en donnant le dossier ou se situe nôtre librairie ainsi que son nom, lors de l’exécution du programme le système ne peut pas magiquement déterminer l’endroit ou il doit aller chercher cette librairie.

Pour que notre programme fonctionne nous avons deux solutions :

Pour le style et l’audace je vous propose d’opter pour la seconde solution, pour se faire il suffit de vous placer à l’aide de votre terminal dans le répertoire ou se situe votre librairie, et de procéder comme ceci :

export LD_LIBRARY_PATH=$(pwd)

Maintenant exécutons notre programme de nouveau :

 

Super ça marche ! Il est également possible d’obtenir plus d’information sur votre librairie dynamique en utilisant une fois de plus la commande ldd :

ldd kali_lib.so

 

On voit bien ici l’utilisation des librairie du système, que ce soit pour le système lui même mais également pour utiliser les fonctions de nôtre include (stdio.h).

V) La sécurité s’apprend aussi dans les librairies

« Bla bla bla il faut que vous lisiez plus, faut arrêter d’aller voir des tutos Youtube qui vous montrent sans expliquer nianianiania.. »

Calmez vous donc, moi qui comptais simplement vous montrer un truc marrant…

« Bon… vas y crache le morceau ! »

Vous vous souvenez que dans la partie précédente nous avons utilisé la variable d’environnement..

« LD_LIBRARY_PATH !!! »

Exactement, et bien sachez qu’il existe une autre variable d’environnement qui peut permettre, entre autre, d’effectuer du détournement d’appel systèmes (Hijack) !

Je tiens à le préciser cette dernière partie du tuto va être légèrement plus complexe que les précédentes, mais pas moins intéressante.

Bien, ceci étant dit, commençons, je vous présente donc la variable d’environnement ‘LD_PRELOAD’. Comme son nom peut le laisser penser, c’est une variable qui permet de spécifier une bibliothèque partagée qui sera chargée au démarrage d’un programme.

La ou cela devient intéressant c’est qu’il va nous être possible de charger une libraire au lancement du programme, et le programme ira se servir en priorité dans cette librairie avant de chercher des définitions de fonctions dans les autres. Je pense que la ça devient un peu flou, vous savez quoi ? Petit exemple simple, prenons ce programme :

//ld_pre.c

#include <stdio.h>

#include <unistd.h>

 

int main()

{

        puts("Bonjour\n");

        return 0;

}

Compilons le :

gcc ld_pre.c -o ld_pre

Et finalement lançons le :

 

Bon, rien de compliqué pour l’instant, maintenant regardons ce second programme :

//ld_pre_evil.c

#include <stdio.h>

 

int puts(const char *s)

{

        printf("Skymble is Awesome !");

        return 0;

}

Transformons le en librairie partagée :

gcc ld_pre_evil.c -shared -fPIC -o ld_pre_evil.so

Oui vous avez vu cette fois on fait tout en un seul mouvement, si j’ai pris le temps de détailler sur les exemples précédents, c’était pour être un maximum didactique. Ici on créer directement la librairie en une seule commande, mais cela reviens finalement au même que de procéder comme dans les exemples précédents.

Maintenant définissons notre variable d’environnement ‘LD_PRELOAD’ :

export LD_PRELOAD=$(pwd)/ld_pre_evil.so

relançons maintenant notre premier programme vous savez celui qui dis « Bonjour » :

 

Ah ! on voit que sans même avoir modifié notre programme l’affichage a changé !

Vous vous souvenez de quand je disais que le système irait chercher en priorité les définitions de fonctions de la librairie mise dans la variable ‘LD_PRELOAD’ ? Et bien c’est exactement ce qu’il vient de se passer ! Sans même toucher à nôtre programme d’origine, nous avons définis une librairie qui « surcharge » la fonction puts, en lui donnant un comportement différent. Une fois la librairie compilée nous avons donner un chemin à la variable ‘LD_PRELOAD’ en y mettant le chemin d’accès de nôtre librairie. En relançant le programme, le système est en priorité aller chercher une définition de la fonction puts dans nôtre librairie, et en a bien trouvé une, il l’a donc utilisé à la place du comportement habituel que l’on aurait eu avec la « vraie » fonction puts.

Une façon de le vérifier autrement est d’utiliser une fois de plus l’outil ldd :

ldd ./ld_pre

 

Nous remarquons que quand la variable ‘LD_PRELOAD’ est définie, notre librairie apparait avant la libc (ou est normalement définie la fonction puts) dans la liste.

Pour ceux qui se posent la question de pourquoi, alors que j’ai dis que ‘LD_PRELOAD’ permettait de charger en PREMIER notre librairie, nous voyons au contraire ici que c’est ‘linux-vdso.so.1’ qui est chargée en premier, sachez que c’est un comportement normal, cette mystérieuse librairie est une librairie virtuelle qui est chargée automatiquement par le kernel dans l’espace d’adressage du programme. Pour faire simple car ce n’est pas le sujet ici, cette librairie permet notamment d’optimiser et d’accélérer les appels systèmes.

On peut même doublement vérifier, en effet, si l’on vide le contenu de ‘LD_PRELOAD’ de cette façon par exemple :

export LD_PRELOAD=

et que l’on relance la même commande ldd que précédemment :

 

L’appel à notre librairie disparait bien, sans même que nous ayons recompiler notre programme.

VI) La Conclusion tant attendue… ou pas ?

Bon bon bon, j’espère d’abord que ce tuto aura été agréable à lire et que vous aurez au moins appris quelque chose ! Le cas contraire et bien….. Dommage ! Je suis conscient que certains me reprocherons de n’avoir pas été assez loin dans certaines explications, et à contrario d’autre me dirons que j’ai été trop dans les détails, mais j’ai essayé de faire de mon mieux pour avoir un tout cohérent et digeste.

Je pourrais vous parler encore de nombreuses heures durant de ‘LD_PRELOAD’ mais je pense que vous avez compris le principe : Il est possible de remplacer des appels à des fonctions à condition de savoir à quelles fonctions nous avons à faire.

Pour ceux qui voudront en savoir un peu plus sur ce sujet ou voir des exemples plus complexes , je vous mets à disposition deux ressources traitant de ce sujet, elles ont en plus, l’avantage d’être en français :

La seule limite sera votre imagination, et la loi aussi… 😀

Merci à tous d’avoir suivi ce tuto et à la prochaine je l’espère !

ZeR0-@bSoLu